package jutf8search;

import java.util.Vector;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * A class containing all the methods for performing the exact searching algorithms.
 * The constructor specifies which algorithm should be used. 
 *
 */
public class ExactSearch
{
	//int chunkSize = 20971520; //20mb
	int chunkSize = 14052; 
    static final int ALPHABET_SIZE = 256;
    ExactSearchInterface s;
   
    /**
     * Constructor method
     *
     * @param algo Specification of search algorithm to use; either Boyer-Moore:
     * "BM", Boyer-Moore-Horespool: "BMH" or Knuth-Morris-Pratt: "KNP"
     */
    ExactSearch(String algo)
    {
    	if(algo.equalsIgnoreCase("BM")) s = new BM();
    	else if(algo.equalsIgnoreCase("BMH")) s = new BMH();
    	else if(algo.equalsIgnoreCase("KMP")) s = new KMP();
    	else System.err.println("Invalid algorithm description");
    }

    /**
     * A function to check for the existence of the loaded query in a file.
     *
     * @param text The text to be searched
     * @return A boolean indicating if the query was found in the file.
     */
    public boolean exists(byte[] text)
    {
		if(s.matchNum(text, 1) != -1) return true;
		else return false;
	}

    /**
     * A function to check for the existence of the loaded query in a file.
     *  
     * @param filename The file to search
     * @throws IOException If there is a problem with reading the file
     * @return A boolean indicating if the query was found in the file.
     */
	public boolean exists(String filename) throws IOException
    {
		FileIO file = new FileIO(filename, chunkSize, s.getPatternSize());
		byte[] text;
		long pos;
		while((text = file.readChunk()) != null)
        {
			pos = s.matchNum(text, 1);
			if(pos != -1) return true;
		}
		return false;
	}
	
    /**
     * Calculates the number of matches of a query in the given byte[].
     *
     * @param text The text to search
     * @return The number of matches found 
     */
    public long numMatches(byte[] text)
    {
		return s.numMatches(text);
	}

    /**
     * Calculates the number of matches of a query in the specified file.
     *
     * @param filename The file to be searched
     * @throws IOException If there is a problem with reading the file
     * @return The number of matches found
     */
	public long numMatches(String filename) throws IOException
	{
		FileIO file = new FileIO(filename, chunkSize, s.getPatternSize());
		byte[] text;
		long matches = 0;
		while((text = file.readChunk()) != null)
        {
			matches += s.numMatches(text);
        }
		return matches;
	}

    /**
     * Calculates the character position of the first match of the query in the given byte[]
     * 
     * @param text The text to search
     * @return The character position of the first match
     */
	public long charPos(byte[] text)
    {
		return Utilities.countChars(text, s.matchNum(text, 1));
	}
	
    /**
     * Calculates the character position of the first match of the query in the given file.
     * 
     * @param text The file to search
     * @throws IOException If there is a problem with reading the file
     * @return The character position of the first match
     */
	public long charPos(String filename) throws IOException
	{
		return charPos(filename, 1);
	}
	
    /**
     * Calculates the character position of the nth match of the query
     * 
     * @param text The text to search
     * @param matchNum Match number n to find.
     * @return The character position of the match, -1 if the query is not present n times.
     */
	public long charPos(byte[] text, int matchNum)
    {
		return Utilities.countChars(text, s.matchNum(text, matchNum));
	}
	
    /**
     * Calculates the character position of the nth match of the query in the given file.
     * 
     * @param text The file to search
     * @param matchNum match number n
     * @throws IOException If there is a problem with reading the file
     * @return The character position of the match, -1 if the query is not found n times in the file.
     */
    public long charPos(String filename, int matchNum) throws IOException
	{
		FileIO file = new FileIO(filename, chunkSize, s.getPatternSize());
		byte[] text;
		long pos = 0;
		long charPos = 0;
		s.resetMatch();
		//byte[] last = null;
		while((text = file.readChunk()) != null)
        {
			pos = s.matchNum(text, matchNum);
			if(pos != -1) return charPos+Utilities.countChars(text,pos);
			else charPos += Utilities.countChars(text,text.length-s.getPatternSize()+1);
			//last = text;
        }
		//byte[] temp = new byte[query.length-1];
		//System.arraycopy(last,last.length-query.length+1,temp,0,temp.length);
		//return charPos+countChars(temp,temp.length);
		return -1;
	}

    /**
     * Calculates the byte position of the first match in the query in the given byte[].
     *
     * @param text The text to search
     * @return The byte position of the first match, -1 if the query is not found.
     */
	public long bytePos(byte[] text)
    {
		return s.matchNum(text, 1);
	}

    /**
     * Calculates the byte position of the first match of the query in the file.
     *
     * @param filename The file to search
     * @throws IOException If there is a problem with reading the file
     * @return The byte position of the first match
     */
	public long bytePos(String filename) throws IOException
    {
		return bytePos(filename, 1);
	}
	
    /**
     * Calculates the byte position of the matchNum-th occurence of the query in the given byte[].
     *
     * @param text The text to search
     * @param matchNum The match number to report
     * @return The byte position of the match, -1 if query not found matchNum times.
     */
	public long bytePos(byte[] text, int matchNum)
    {
		return s.matchNum(text, matchNum);
	}
	
    /**
     * Calculates the byte position of the matchNum-th occurence of the query in the specified file.
     *
     * @param filename The file to search
     * @param matchNum The match number to report
     * @throws IOException If there is a problem with reading the file
     * @return The byte position of the match, -1 if not found matchNum times.
     */
	public long bytePos(String filename, int matchNum) throws IOException
	{
		FileIO file = new FileIO(filename, chunkSize, s.getPatternSize());
		byte[] text;
		long pos = 0;
		long totalPos = 0;
		s.resetMatch();
		while((text = file.readChunk()) != null)
        {
			pos = s.matchNum(text, matchNum);
			if(pos != -1) return totalPos+pos;
			else totalPos += file.chunk.length-(s.getPatternSize()-1);
        }
		//return totalPos+s.getPatternSize()-1;
		return -1;
	}

    /**
     * Returns a Vector containing the byte positions of every occurence
     *  of the pattern in the given byte[].
     *
     * @param text The text to search
     * @return Vector of results
     */ 
    public Vector<Long> fullSearch(byte[] text)
    {
        return s.fullSearch(text);
    }
    
    /**
     * Returns a Vector containing the byte positions of each match of the query
     *  in the given file.
     *
     * @param text The file to search
     * @return Vector of results
     */ 
    public Vector<Long> fullSearch(String filename) throws IOException
    {
		FileIO file = new FileIO(filename, chunkSize, s.getPatternSize());
		byte[] text;
        Vector<Long> part, results = new Vector<Long>();
        long bytesRead = 0;
        int numChunksRead = 0;
		while((text = file.readChunk()) != null)
        {
			part = s.fullSearch(text);
            for(int i=0; i<part.size(); i++)
            {
                long a = ((Long)(part.get(i))).longValue();
                results.add(new
                        Long(a+bytesRead-((numChunksRead)*(s.getPatternSize()-1))));
            }
            part.clear();
            bytesRead+=text.length;
            numChunksRead++;
        }
        return results;
    }
	
    /**
	 * Save the search table using Serialization to the file specified.
	 * @param filename The filename to save the search table to.
	 * @throws IOException If the file cannot be written.
	 */
    public void saveTable(String filename) throws IOException
    {
		FileOutputStream fos = new FileOutputStream(filename);
		ObjectOutputStream out = new ObjectOutputStream(fos);
		out.writeObject(s);
		out.close();
	}
    
    /**
     * Load a saved search table from the specified file.
     * @param filename The filename to load from.
     * @throws IOException If there was an error reading the file.
     * @throws ClassNotFoundException If the class being loaded cannot be found.
     */
    public void loadTable(String filename) throws IOException, ClassNotFoundException
    {
		FileInputStream fis = new FileInputStream(filename);
		ObjectInputStream in = new ObjectInputStream(fis);
		s = (ExactSearchInterface)in.readObject();
	}
    
    /**
     * Build the search table for the specified query.
     * @param query The query to be searched for.
     * @throws Exception If there is a problem building the table (for example, the query is too short).
     */
    public void buildTable(String query) throws Exception
    {
    	s.buildTable(query);
    }
    
    /**
     * Build the search table for the specified query.
     * @param query The query to be searched for.
     * @throws Exception If there is a problem building the table (for example, the query is too short for the given algorithm).
     */
	public void buildTable(byte[] query) throws Exception
    {
		s.buildTable(query);
	}
    
    /**
     * Get the size of the pattern in bytes.
     * @return The size of the pattern in bytes
     */
    public int getPatternSize()
    {
        return s.getPatternSize();
    }

	/**
	 * Set the chunkSize used when reading the file.
	 * @param chunkSize The chunkSize to be used.
	 */
	public void setChunkSize(int chunkSize)
    {
		this.chunkSize = chunkSize;
	}
}
